<?php
/**
 * This file contains the ESDBCommand class.
 *
 * @author Yoyon Cahyono <yoyoncahyono@gmail.com>
 * @link http://ondev.enggon.com/
 * @copyright Copyright &copy; 2011 Enggon.com
 * @license http://www.enggon.com/license/
 */

/**
 * ESDBCommand represents an SQL statement to execute against a database.
 *
 * It is usually created by calling {@link ESDBConnection::createCommand}.
 * The SQL statement to be executed may be set via {@link setText Text}.
 *
 * To execute an SQL statement that returns result data set
 * (such as SELECT), use {@link query} or its convenient versions {@link queryRow},
 * {@link queryColumn}, or {@link queryScalar}.
 *
 * If an SQL statement returns results (such as a SELECT SQL), the results
 * can be accessed via the returned {@link ESDBDataReader}.
 *
 * ESDBCommand supports SQL statment preparation and parameter binding.
 * Call {@link bindParam} to bind a PHP variable to a parameter in SQL.
 * Call {@link bindValue} to bind a value to an SQL parameter.
 * When binding a parameter, the SQL statement is automatically prepared.
 * You may also call {@link prepare} to explicitly prepare an SQL statement.
 *
 * ESDBCommand can also be used as a query builder
 * that builds a SQL statement from code fragments. For example,
 * <pre>
 * $user = Yii::app()->sdb->createCommand()
 *     ->select('username, password')
 *     ->from('tbl_user')
 *     ->where('id=:id', array(':id'=>1))
 *     ->queryRow();
 * </pre>
 *
 * @author Yoyon Cahyono <yoyoncahyono@gmail.com>
 * @version $Id: ESDBCommand.php 1 2011-12-28 19:22:47Z yoyoncahyono $
 * @package ext.yii-simpledb
 */
class ESDBCommand extends CComponent
{
	/**
	 * @var array the parameters (name=>value) to be bound to the current query.
	 */
	public $params=array();

	private $_connection;
	private $_text;
	private $_paramLog=array();
	private $_query;

	/**
	 * Constructor.
	 * @param ESDBConnection $connection the database connection
	 * @param mixed $query the DB query to be executed. This can be either
	 * a string representing a SQL statement, or an array whose name-value pairs
	 * will be used to set the corresponding properties of the created command object.
	 *
	 * For example, you can pass in either <code>'SELECT * FROM tbl_user'</code>
	 * or <code>array('select'=>'*', 'from'=>'tbl_user')</code>. They are equivalent
	 * in terms of the final query result.
	 *
	 * When passing the query as an array, the following properties are commonly set:
	 * {@link select}, {@link distinct}, {@link from}, {@link where}, {@link order}
	 * and {@link limit}.
	 * Please refer to the setter of each of these properties for details
	 * about valid property values.
	 *
	 */
	public function __construct(ESDBConnection $connection,$query=null)
	{
		$this->_connection=$connection;
		if(is_array($query))
		{
			foreach($query as $name=>$value)
				$this->$name=$value;
		}
		else
			$this->setText($query);
	}

	/**
	 * Cleans up the command and prepares for building a new query.
	 * This method is mainly used when a command object is being reused
	 * multiple times for building different queries.
	 * Calling this method will clean up all internal states of the command object.
	 * @return ESDBCommand this command instance
	 */
	public function reset()
	{
		$this->_text=null;
		$this->_query=null;
		$this->_paramLog=array();
		$this->params=array();
		return $this;
	}

	/**
	 * @return string the SQL statement to be executed
	 */
	public function getText()
	{
		if($this->_text=='' && !empty($this->_query))
			$this->setText($this->buildQuery($this->_query));
		return $this->_text;
	}

	/**
	 * Specifies the SQL statement to be executed.
	 * Any previous execution will be terminated or cancel.
	 * @param string $value the SQL statement to be executed
	 * @return ESDBCommand this command instance
	 */
	public function setText($value)
	{
		if($this->_connection->tablePrefix!==null && $value!='')
			$this->_text=preg_replace('/{{(.*?)}}/',$this->_connection->tablePrefix.'\1',$value);
		else
			$this->_text=$value;
		return $this;
	}

	/**
	 * @return ESDBConnection the connection associated with this command
	 */
	public function getConnection()
	{
		return $this->_connection;
	}

	/**
	 * Executes the SQL statement and returns query result.
	 * This method is for executing an SQL query that returns result set.
	 * @param array $params input parameters (name=>value) for the SQL execution.
	 * @param string $nextToken select the SQL using the nextToken for the SQL execution.
	 * @return array all rows of the query result. Each array element is an array representing a row.
	 * @throws CException execution failed
	 */
	public function query($params=array(),$nextToken = null)
	{
		$params=array_merge($this->params,$params);
		krsort($params);
		foreach($params as $name=>$value) {
			$this->_query['where']=str_replace($name, $this->_connection->quoteValue($value), $this->_query['where']);
		}
 		Yii::trace('Querying SQL: '.$this->getText(),'ext.yii-simpledb.ESDBCommand');
		//ignore getNextToken if nextToken already defined
 		if (!$nextToken && $this->getOffset()>0) {
			$nextToken = $this->getNextToken($this->getLimit(),$this->getOffset());
		}
 		if($this->_connection->queryCachingCount>0 && $this->_connection->queryCachingDuration>0
				&& $this->_connection->queryCacheID!==false
				&& ($cache=Yii::app()->getComponent($this->_connection->queryCacheID))!==null)
		{
			$this->_connection->queryCachingCount--;
			$cacheKey='ESDB:query:'.$this->_connection->accessKey;
			$cacheKey.=':'.$this->getText().':'.$nextToken?$nextToken:'NULL';
			if(($result=$cache->get($cacheKey))!==false)
			{
				Yii::trace('Query result found in cache','ext.yii-simpeldb.ESDBCommand');
				return $result;
			}
		}
 		try
		{
			if($this->_connection->enableProfiling)
				Yii::beginProfile('ext.yii-simpledb.ESDBCommand.query('.$this->getText().')','ext.yii-simpledb.ESDBCommand.query');
			$result = $this->_connection->select($this->getText(),$nextToken);
			if($this->_connection->enableProfiling)
				Yii::endProfile('ext.yii-simpledb.ESDBCommand.query('.$this->getText().')','ext.yii-simpledb.ESDBCommand.query');
			if(isset($cache,$cacheKey))
				$cache->set($cacheKey, $result, $this->_connection->queryCachingDuration, $this->_connection->queryCachingDependency);
			return $result;
		}
		catch(Exception $e)
		{
			if($this->_connection->enableProfiling)
				Yii::endProfile('ext.yii-simpledb.ESDBCommand.query('.$this->getText().')','ext.yii-simpledb.ESDBCommand.query');
            $errorInfo = $e instanceof PDOException ? $e->errorInfo : null;
            $message = $e->getMessage();
			Yii::log(Yii::t('yii','ESDBCommand::query() failed: {error}. The SQL statement executed was: {sql}.',
				array('{error}'=>$message, '{sql}'=>$this->getText())),CLogger::LEVEL_ERROR,'ext.yii-simpledb.ESDBCommand');
            if(YII_DEBUG)
            	$message .= '. The SQL statement executed was: '.$this->getText();
			throw new ESDBException(__METHOD__,Yii::t('yii','ESDBCommand failed to execute the SQL statement: {error}',
				array('{error}'=>$message)));
		}
	}
	/**
	 * Get the NextToken for the given query based on its limit and offset.
	 * @param integer $limit the query limit.
	 * @param integer $offset the query offset.
	 * @return string the NextToken for the query
	 */
	public function getNextToken($limit, $offset)
	{
		if($offset>0) {
			if ($limit) {
				$metadata = $this->_connection->domainMetadata($this->getDomain());
				if(($offset) >= $metadata['itemCount'])
					throw new ESDBException('No item found beyond offset');
				$this->setCount(true);
				$this->setLimit($offset);
				$this->_connection->select($this->getText());
				$this->setCount(false);
				$this->setLimit($limit);
			}
		}
		return $this->_connection->getNextToken();
	}
	/**
	 * Builds a SQL SELECT statement from the given query specification.
	 * @param array $query the query specification in name-value pairs. The following
	 * query options are supported: {@link select}, {@link distinct}, {@link from},
	 * {@link where}, {@link order}, {@link limit} and {@link offset}.
	 * @return string the SQL statement
	 */
	public function buildQuery($query)
	{
		if(@$query['count'])
			$sql ='SELECT COUNT(*) ';
		else if(@$query['itemName'])
			$sql ='SELECT itemName() ';
		else
			$sql ='SELECT '.(isset($query['select']) ? $query['select'] : '*');
		
		if(isset($query['from']))
			$sql.="\nFROM ".$query['from'];
		else
			throw new ESDBException('The DB query must contain the "from" portion.');

		if(isset($query['where']))
			$sql.="\nWHERE ".$query['where'];

		if(isset($query['order']))
			$sql.="\nORDER BY ".$query['order'];

		$limit=isset($query['limit']) ? (int)$query['limit'] : -1;
		$offset=isset($query['offset']) ? (int)$query['offset'] : -1;
		if($limit>0 )
			$sql.="\nLIMIT ".$query['limit'];
		return $sql;
	}

	/**
	 * Sets the SELECT part of the query.
	 * @param mixed $columns the columns to be selected. Defaults to '*', meaning all columns.
	 * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
	 * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
	 * The method will automatically quote the column names unless a column contains some parenthesis
	 * (which means the column contains a DB expression).
	 * @return ESDBCommand the command object itself
	 
	 */
	public function select($columns='*')
	{
		if(is_string($columns) && strpos($columns,'(')!==false)
			$this->_query['select']=$columns;
		else
		{
			if(!is_array($columns))
				$columns=preg_split('/\s*,\s*/',trim($columns),-1,PREG_SPLIT_NO_EMPTY);

			foreach($columns as $i=>$column)
			{
				if(is_object($column))
					$columns[$i]=(string)$column;
				else if(strpos($column,'(')===false)
				{
					if(preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/',$column,$matches))
						$columns[$i]=$this->_connection->quoteColumnName($matches[1]).' AS '.$this->_connection->quoteColumnName($matches[2]);
					else
						$columns[$i]=$this->_connection->quoteColumnName($column);
				}
			}
			$this->_query['select']=implode(', ',$columns);
		}
		return $this;
	}

	/**
	 * Returns the SELECT part in the query.
	 * @return string the SELECT part (without 'SELECT') in the query.
	 
	 */
	public function getSelect()
	{
		return isset($this->_query['select']) ? $this->_query['select'] : '';
	}

	/**
	 * Sets the SELECT part in the query.
	 * @param mixed $value the data to be selected. Please refer to {@link select()} for details
	 * on how to specify this parameter.
	 
	 */
	public function setSelect($value)
	{
		$this->select($value);
	}

	/**
	 * Sets the SELECT part of the query with the ItemName() turned on.
	 * This is the same as {@link select} except that the ItemName() is turned on.
	 * @return ESDBCommand the command object itself
	 
	 */
	public function selectItemName()
	{
		$this->_query['itemName']=true;
		$this->_query['count']=false;
		return $this->select();
	}
	
	/**
	 * Returns a value indicating whether SELECT itemName() should be used.
	 * @return boolean a value indicating whether SELECT itemName() should be used.
	 
	 */
	public function getItemName()
	{
		return isset($this->_query['itemName']) ? $this->_query['itemName'] : false;
	}

	/**
	 * Sets a value indicating whether SELECT itemName() should be used.
	 * @param boolean $value a value indicating whether SELECT itemName() should be used.
	 
	 */
	public function setItemName($value)
	{
		$this->_text = null;
		$this->_query['itemName']=$value;
		if ($value) {
			$this->_query['count']=false;
		}
	}
	
	/**
	 * Sets the SELECT part of the query with the COUNT(*) turned on.
	 * This is the same as {@link select} except that the COUNT(*) is turned on.
	 * @return ESDBCommand the command object itself
	 
	 */
	public function selectCount()
	{
		$this->_query['count']=true;
		$this->_query['itemName']=false;
		return $this->select();
	}

	/**
	 * Returns a value indicating whether SELECT COUNT(*) should be used.
	 * @return boolean a value indicating whether SELECT COUNT(*) should be used.
	 
	 */
	public function getCount()
	{
		return isset($this->_query['count']) ? $this->_query['count'] : false;
	}

	/**
	 * Sets a value indicating whether SELECT COUNT(*) should be used.
	 * @param boolean $value a value indicating whether SELECT COUNT(*) should be used.
	 
	 */
	public function setCount($value)
	{
		$this->_text = null;
		$this->_query['count']=$value;
		if ($value) {
			$this->_query['itemName']=false;
		}
	}

	/**
	 * Sets the FROM part of the query.
	 * @param string $domain the domain to be selected from.
	 * The method will automatically quote the table names
	 * @return ESDBCommand the command object itself
	 
	 */
	public function from($domain)
	{
		if(is_string($domain) && strpos($domain,'(')===false)
			$this->_query['from']=$this->_connection->quoteTableName($domain);
		else
			throw new ESDBException(__METHOD__, 'Domain name is invalid.');
		return $this;
	}

	/**
	 * Returns the FROM part in the query.
	 * @return string the FROM part (without 'FROM' ) in the query.
	 */
	public function getFrom()
	{
		return isset($this->_query['from']) ? $this->_query['from'] : '';
	}

	/**
	 * Sets the FROM part in the query.
	 * @param mixed $value the tables to be selected from. Please refer to {@link from()} for details
	 * on how to specify this parameter.
	 */
	public function setFrom($value)
	{
		$this->from($value);
	}
	/**
	 * Returns the domain name part in the query.
	 * @return string the domain part (from FROM part in the query without backquote ).
	 */
	public function getDomain()
	{
		return isset($this->_query['from']) ? trim($this->_query['from'],'`') : '';
	}
	
	/**
	 * Sets the WHERE part of the query.
	 *
	 * The method requires a $conditions parameter, and optionally a $params parameter
	 * specifying the values to be bound to the query.
	 *
	 * The $conditions parameter should be either a string (e.g. 'id=1') or an array.
	 * If the latter, it must be of the format <code>array(operator, operand1, operand2, ...)</code>,
	 * where the operator can be one of the followings, and the possible operands depend on the corresponding
	 * operator:
	 * <ul>
	 * <li><code>and</code>: the operands should be concatenated together using AND. For example,
	 * array('and', 'id=1', 'id=2') will generate 'id=1 AND id=2'. If an operand is an array,
	 * it will be converted into a string using the same rules described here. For example,
	 * array('and', 'type=1', array('or', 'id=1', 'id=2')) will generate 'type=1 AND (id=1 OR id=2)'.
	 * The method will NOT do any quoting or escaping.</li>
	 * <li><code>or</code>: similar as the <code>and</code> operator except that the operands are concatenated using OR.</li>
	 * <li><code>in</code>: operand 1 should be a column or DB expression, and operand 2 be an array representing
	 * the range of the values that the column or DB expression should be in. For example,
	 * array('in', 'id', array(1,2,3)) will generate 'id IN (1,2,3)'.
	 * The method will properly quote the column name and escape values in the range.</li>
	 * <li><code>not in</code>: similar as the <code>in</code> operator except that IN is replaced with NOT IN in the generated condition.</li>
	 * <li><code>like</code>: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
	 * the values that the column or DB expression should be like.
	 * For example, array('like', 'name', '%tester%') will generate "name LIKE '%tester%'".
	 * When the value range is given as an array, multiple LIKE predicates will be generated and concatenated using AND.
	 * For example, array('like', 'name', array('%test%', '%sample%')) will generate
	 * "name LIKE '%test%' AND name LIKE '%sample%'".
	 * The method will properly quote the column name and escape values in the range.</li>
	 * <li><code>not like</code>: similar as the <code>like</code> operator except that LIKE is replaced with NOT LIKE in the generated condition.</li>
	 * <li><code>or like</code>: similar as the <code>like</code> operator except that OR is used to concatenated the LIKE predicates.</li>
	 * <li><code>or not like</code>: similar as the <code>not like</code> operator except that OR is used to concatenated the NOT LIKE predicates.</li>
	 * </ul>
	 * @param mixed $conditions the conditions that should be put in the WHERE part.
	 * @param array $params the parameters (name=>value) to be bound to the query
	 * @return ESDBCommand the command object itself
	 */
	public function where($conditions, $params=array())
	{
		$this->_query['where']=$this->processConditions($conditions);
		foreach($params as $name=>$value) {
			$this->params[$name]=$value;
		}
		return $this;
	}

	/**
	 * Returns the WHERE part in the query.
	 * @return string the WHERE part (without 'WHERE' ) in the query.
	 */
	public function getWhere()
	{
		return isset($this->_query['where']) ? $this->_query['where'] : '';
	}

	/**
	 * Sets the WHERE part in the query.
	 * @param mixed $value the where part. Please refer to {@link where()} for details
	 * on how to specify this parameter.
	 */
	public function setWhere($value)
	{
		$this->where($value);
	}

	/**
	 * Sets the ORDER BY part of the query.
	 * @param mixed $columns the columns (and the directions) to be ordered by.
	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
	 * The method will automatically quote the column names unless a column contains some parenthesis
	 * (which means the column contains a DB expression).
	 * @return ESDBCommand the command object itself
	 
	 */
	public function order($columns)
	{
		if(is_string($columns) && strpos($columns,'(')!==false)
			$this->_query['order']=$columns;
		else
		{
			if(!is_array($columns))
				$columns=preg_split('/\s*,\s*/',trim($columns),-1,PREG_SPLIT_NO_EMPTY);
			foreach($columns as $i=>$column)
			{
				if(is_object($column))
					$columns[$i]=(string)$column;
				else if(strpos($column,'(')===false)
				{
					if(preg_match('/^(.*?)\s+(asc|desc)$/i',$column,$matches))
						$columns[$i]=$this->_connection->quoteColumnName($matches[1]).' '.strtoupper($matches[2]);
					else
						$columns[$i]=$this->_connection->quoteColumnName($column);
				}
			}
			$this->_query['order']=implode(', ',$columns);
		}
		return $this;
	}

	/**
	 * Returns the ORDER BY part in the query.
	 * @return string the ORDER BY part (without 'ORDER BY' ) in the query.
	 
	 */
	public function getOrder()
	{
		return isset($this->_query['order']) ? $this->_query['order'] : '';
	}

	/**
	 * Sets the ORDER BY part in the query.
	 * @param mixed $value the ORDER BY part. Please refer to {@link order()} for details
	 * on how to specify this parameter.
	 
	 */
	public function setOrder($value)
	{
		$this->order($value);
	}

	/**
	 * Sets the LIMIT part of the query.
	 * @param integer $limit the limit
	 * @param integer $offset the offset
	 * @return ESDBCommand the command object itself
	 
	 */
	public function limit($limit, $offset=null)
	{
		$this->_query['limit']=(int)$limit;
		if($offset!==null)
			$this->offset($offset);
		return $this;
	}

	/**
	 * Returns the LIMIT part in the query.
	 * @return string the LIMIT part (without 'LIMIT' ) in the query.
	 
	 */
	public function getLimit()
	{
		return isset($this->_query['limit']) ? $this->_query['limit'] : -1;
	}

	/**
	 * Sets the LIMIT part in the query.
	 * @param integer $value the LIMIT part. Please refer to {@link limit()} for details
	 * on how to specify this parameter.
	 
	 */
	public function setLimit($value)
	{
		$this->limit($value);
	}

	/**
	 * Sets the OFFSET part of the query.
	 * @param integer $offset the offset
	 * @return ESDBCommand the command object itself
	 
	 */
	public function offset($offset)
	{
		$this->_query['offset']=(int)$offset;
		return $this;
	}

	/**
	 * Returns the OFFSET part in the query.
	 * @return string the OFFSET part (without 'OFFSET' ) in the query.
	 
	 */
	public function getOffset()
	{
		return isset($this->_query['offset']) ? $this->_query['offset'] : -1;
	}

	/**
	 * Sets the OFFSET part in the query.
	 * @param integer $value the OFFSET part. Please refer to {@link offset()} for details
	 * on how to specify this parameter.
	 
	 */
	public function setOffset($value)
	{
		$this->offset($value);
	}
	
	/**
	 * @param string $domain the domain name to be created
	 * @return boolean create domain operation success state
	 */
	public function create($domain) {
		return $this->_connection->createDomain($domain);
	}
	/**
	 * Generates the condition string that will be put in the WHERE part
	 * @param mixed $conditions the conditions that will be put in the WHERE part.
	 * @return string the condition string to put in the WHERE part
	 */
	private function processConditions($conditions)
	{
		if(!is_array($conditions))
			return $conditions;
		else if($conditions===array())
			return '';
		$n=count($conditions);
		$operator=strtoupper($conditions[0]);
		if($operator==='OR' || $operator==='AND')
		{
			$parts=array();
			for($i=1;$i<$n;++$i)
			{
				$condition=$this->processConditions($conditions[$i]);
				if($condition!=='')
					$parts[]='('.$condition.')';
			}
			return $parts===array() ? '' : implode(' '.$operator.' ', $parts);
		}

		if(!isset($conditions[1],$conditions[2]))
			return '';

		$column=$conditions[1];
		if(strpos($column,'(')===false)
			$column=$this->_connection->quoteColumnName($column);

		$values=$conditions[2];
		if(!is_array($values))
			$values=array($values);

		if($operator==='IN' || $operator==='NOT IN')
		{
			if($values===array())
				return $operator==='IN' ? '0=1' : '';
			foreach($values as $i=>$value)
			{
				if(is_string($value))
					$values[$i]=$this->_connection->quoteValue($value);
				else
					$values[$i]=(string)$value;
			}
			return $column.' '.$operator.' ('.implode(', ',$values).')';
		}

		if($operator==='LIKE' || $operator==='NOT LIKE' || $operator==='OR LIKE' || $operator==='OR NOT LIKE')
		{
			if($values===array())
				return $operator==='LIKE' || $operator==='OR LIKE' ? '0=1' : '';

			if($operator==='LIKE' || $operator==='NOT LIKE')
				$andor=' AND ';
			else
			{
				$andor=' OR ';
				$operator=$operator==='OR LIKE' ? 'LIKE' : 'NOT LIKE';
			}
			$expressions=array();
			foreach($values as $value)
				$expressions[]=$column.' '.$operator.' '.$this->_connection->quoteValue($value);
			return implode($andor,$expressions);
		}
		throw new ESDBException(Yii::t('yii', 'Unknown operator "{operator}".', array('{operator}'=>$operator)));
	}
	/**
	 * Creates a new item or an attributes if itemName is not null.
	 * The method will properly escape the column names, and bind the values to be inserted.
	 * @param string $domain the domain that new item will be inserted into.
	 * @param array $items the items (itemname=>attributes) to be inserted into domain.
	 * @param string $itemNameGenerator item name user defined function to generate the item name
	 * only affected on item with undefined key as item name on $items array. Defaults to random UUID.
	 * @return boolean status of insert operation.
	 */
	public function insert($domain, $items, $itemNameGenerator = null)
	{
		$itms = array();
		foreach ($items as $key=>$item) {
			if (is_int($key)) {
				if ($itemNameGenerator) {
					$key = call_user_func($itemNameGenerator,$item);
				} else {
					$key = uniqid();
				}
			}
			$itms[$key]=$item;
		}
		if (count($itms)>1) {
			return $this->_connection->batchPutAttributes($domain, $itms,null,false);
		} else {
			return $this->_connection->putAttributes($domain, $itms, null,false);
		}
	}
	/**
	 * Delete attributes or an item if attributes in null.
	 * @param string $domain the domain that new item will be inserted into.
	 * @param mixed $conditions the conditions that should be put in the WHERE part {@link where}.
	 * @param array $params the parameters (name=>value) to be bound to the query
	 * @return boolean status of delete operation.
	 */
	public function delete($domain, $conditions='', $params=array())
	{
		$items = array();
		foreach ($this->selectItemName()->from($domain)->where($conditions,$params)->query() as $item) {
			$items[$item['Name']]=array();
		}
		if (count($items)>0) {
			return $this->_connection->batchDeleteAttributes($domain, $items);
		}
		return false;
	}
	/**
	 * Update an item.
	 * The method will properly escape the column names, and bind the values to be inserted.
	 * @param string $domain the domain that new item will be inserted into.
	 * @param array $attributes the attributes data (name=>value) to be inserted into domain.
	 * @param mixed $conditions the conditions that should be put in the WHERE part {@link where}.
	 * @param array $params the parameters (name=>value) to be bound to the query
	 * @return boolean status of update operation.
	 */
	public function update($domain, $attributes,  $conditions='', $params=array())
	{
		$itemName = $itemName?$itemName:uniqid();
		try {
			$result = $this->_connection->putAttributes($domain, $itemName, $attributes, $expected, true);
		} catch (ESDBException $e) {
			if($e->status == 'SDB::MultiValuedAttribute') {
				$normAttributes = $this->_connection->normalizeAttributes($attributes,false);
				$attr=array();
				foreach ($expected as $name=>$value) {
					if(@$normAttributes[$name]) {
						$attr[]=$name;
					}
				}
				$attr = $this->_connection->getAttributes($domain, $itemName,$attr);
				die(CVarDumper::dumpAsString($attr,10,true));
				$this->delete($domain, $attributes, $itemName);
				$result = $this->insert($domain, $attributes, $itemName);
			}
		}
		return $result;
	}
}
